<html><head><meta charset="utf-8"/><title>方法</title></head><body><h1>方法</h1><div class="x-wiki-content x-main-content">
 <p>
  在一个对象中绑定函数，称为这个对象的方法。
 </p>
 <p>
  在JavaScript中，对象的定义是这样的：
 </p>
 <pre><code>var xiaoming = {
    name: '小明',
    birth: 1990
};
</code></pre>
 <p>
  但是，如果我们给
  <code>
   xiaoming
  </code>
  绑定一个函数，就可以做更多的事情。比如，写个
  <code>
   age()
  </code>
  方法，返回
  <code>
   xiaoming
  </code>
  的年龄：
 </p>
 <pre><code>var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
};

xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年调用是25,明年调用就变成26了
</code></pre>
 <p>
  绑定到对象上的函数称为方法，和普通函数也没啥区别，但是它在内部使用了一个
  <code>
   this
  </code>
  关键字，这个东东是什么？
 </p>
 <p>
  在一个方法内部，
  <code>
   this
  </code>
  是一个特殊变量，它始终指向当前对象，也就是
  <code>
   xiaoming
  </code>
  这个变量。所以，
  <code>
   this.birth
  </code>
  可以拿到
  <code>
   xiaoming
  </code>
  的
  <code>
   birth
  </code>
  属性。
 </p>
 <p>
  让我们拆开写：
 </p>
 <pre><code>function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 25, 正常结果
getAge(); // NaN
</code></pre>
 <p>
  单独调用函数
  <code>
   getAge()
  </code>
  怎么返回了
  <code>
   NaN
  </code>
  ？
  <em>
   请注意
  </em>
  ，我们已经进入到了JavaScript的一个大坑里。
 </p>
 <p>
  JavaScript的函数内部如果调用了
  <code>
   this
  </code>
  ，那么这个
  <code>
   this
  </code>
  到底指向谁？
 </p>
 <p>
  答案是，视情况而定！
 </p>
 <p>
  如果以对象的方法形式调用，比如
  <code>
   xiaoming.age()
  </code>
  ，该函数的
  <code>
   this
  </code>
  指向被调用的对象，也就是
  <code>
   xiaoming
  </code>
  ，这是符合我们预期的。
 </p>
 <p>
  如果单独调用函数，比如
  <code>
   getAge()
  </code>
  ，此时，该函数的
  <code>
   this
  </code>
  指向全局对象，也就是
  <code>
   window
  </code>
  。
 </p>
 <p>
  坑爹啊！
 </p>
 <p>
  更坑爹的是，如果这么写：
 </p>
 <pre><code>var fn = xiaoming.age; // 先拿到xiaoming的age函数
fn(); // NaN
</code></pre>
 <p>
  也是不行的！要保证
  <code>
   this
  </code>
  指向正确，必须用
  <code>
   obj.xxx()
  </code>
  的形式调用！
 </p>
 <p>
  由于这是一个巨大的设计错误，要想纠正可没那么简单。ECMA决定，在strict模式下让函数的
  <code>
   this
  </code>
  指向
  <code>
   undefined
  </code>
  ，因此，在strict模式下，你会得到一个错误：
 </p>
 <pre><code>'use strict';

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
};

var fn = xiaoming.age;
fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined
</code></pre>
 <p>
  这个决定只是让错误及时暴露出来，并没有解决
  <code>
   this
  </code>
  应该指向的正确位置。
 </p>
 <p>
  有些时候，喜欢重构的你把方法重构了一下：
 </p>
 <pre><code>'use strict';

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        function getAgeFromBirth() {
            var y = new Date().getFullYear();
            return y - this.birth;
        }
        return getAgeFromBirth();
    }
};

xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
</code></pre>
 <p>
  结果又报错了！原因是
  <code>
   this
  </code>
  指针只在
  <code>
   age
  </code>
  方法的函数内指向
  <code>
   xiaoming
  </code>
  ，在函数内部定义的函数，
  <code>
   this
  </code>
  又指向
  <code>
   undefined
  </code>
  了！（在非strict模式下，它重新指向全局对象
  <code>
   window
  </code>
  ！）
 </p>
 <p>
  修复的办法也不是没有，我们用一个
  <code>
   that
  </code>
  变量首先捕获
  <code>
   this
  </code>
  ：
 </p>
 <pre><code>'use strict';

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        var that = this; // 在方法内部一开始就捕获this
        function getAgeFromBirth() {
            var y = new Date().getFullYear();
            return y - that.birth; // 用that而不是this
        }
        return getAgeFromBirth();
    }
};

xiaoming.age(); // 25
</code></pre>
 <p>
  用
  <code>
   var that = this;
  </code>
  ，你就可以放心地在方法内部定义其他函数，而不是把所有语句都堆到一个方法中。
 </p>
 <h3>
  apply
 </h3>
 <p>
  虽然在一个独立的函数调用中，根据是否是strict模式，
  <code>
   this
  </code>
  指向
  <code>
   undefined
  </code>
  或
  <code>
   window
  </code>
  ，不过，我们还是可以控制
  <code>
   this
  </code>
  的指向的！
 </p>
 <p>
  要指定函数的
  <code>
   this
  </code>
  指向哪个对象，可以用函数本身的
  <code>
   apply
  </code>
  方法，它接收两个参数，第一个参数就是需要绑定的
  <code>
   this
  </code>
  变量，第二个参数是
  <code>
   Array
  </code>
  ，表示函数本身的参数。
 </p>
 <p>
  用
  <code>
   apply
  </code>
  修复
  <code>
   getAge()
  </code>
  调用：
 </p>
 <pre><code>function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
</code></pre>
 <p>
  另一个与
  <code>
   apply()
  </code>
  类似的方法是
  <code>
   call()
  </code>
  ，唯一区别是：
 </p>
 <ul>
  <li>
   <p>
    <code>
     apply()
    </code>
    把参数打包成
    <code>
     Array
    </code>
    再传入；
   </p>
  </li>
  <li>
   <p>
    <code>
     call()
    </code>
    把参数按顺序传入。
   </p>
  </li>
 </ul>
 <p>
  比如调用
  <code>
   Math.max(3, 5, 4)
  </code>
  ，分别用
  <code>
   apply()
  </code>
  和
  <code>
   call()
  </code>
  实现如下：
 </p>
 <pre><code>Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
</code></pre>
 <p>
  对普通函数调用，我们通常把
  <code>
   this
  </code>
  绑定为
  <code>
   null
  </code>
  。
 </p>
 <h3>
  装饰器
 </h3>
 <p>
  利用
  <code>
   apply()
  </code>
  ，我们还可以动态改变函数的行为。
 </p>
 <p>
  JavaScript的所有对象都是动态的，即使内置的函数，我们也可以重新指向新的函数。
 </p>
 <p>
  现在假定我们想统计一下代码一共调用了多少次
  <code>
   parseInt()
  </code>
  ，可以把所有的调用都找出来，然后手动加上
  <code>
   count += 1
  </code>
  ，不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的
  <code>
   parseInt()
  </code>
  ：
 </p>
 <pre><code class="language-x-javascript">'use strict';

var count = 0;
var oldParseInt = parseInt; // 保存原函数

window.parseInt = function () {
    count += 1;
    return oldParseInt.apply(null, arguments); // 调用原函数
};
----
// 测试:
parseInt('10');
parseInt('20');
parseInt('30');
console.log('count = ' + count); // 3
</code></pre>
</div>
</body></html>